import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { Vector3, Quaternion, Matrix } from '@babylonjs/core/Maths/math.vector';
import { Scene } from '@babylonjs/core/scene';
import { Utils } from '../tool/utils';
import { Color3 } from '@babylonjs/core/Maths/math.color';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { LinesMesh } from '@babylonjs/core/Meshes/linesMesh';
import { Calculation } from '../tool/calculation';

/**
 * 步进回调函数
 * @param previousT 上一个进程值
 * @param currentT 当前进程值
 */
interface IStepCallback {
    (previousT: number, currentT: number): void
}

/**
 * 同步回调函数
 * @param target 动画对象
 */
interface ISynchronizeCallback {
    (target: AnimateTarget): void
}

/**
 * 动画对象
 */
class AnimateTarget {

    private _object: TransformNode;
    private _positionT: number = 0;
    private _speedT: number;
    private _speedDir: number = 1;
    private _offsetU: number;
    private _offsetV: number;
    private _preT: number = 0;
    private _stepCallback: IStepCallback | null = null;
    private _syncCallback: ISynchronizeCallback | null = null;
    private _tV0 = new Vector3();
    private _tV1 = new Vector3();
    private _tV2 = new Vector3();
    private _rot = new Quaternion();
    private _tQ0 = new Quaternion();
    private _tQ1 = new Quaternion();
    private _tM0 = new Matrix();
    private _tM1 = new Matrix();
    private _tM2 = new Matrix();
    private _idx: [number, number] = [0, 0];

    constructor(object: TransformNode, speedT: number, offsetU: number, offsetV: number) {
        this._object = object;
        this._speedT = speedT;
        this._offsetU = offsetU;
        this._offsetV = offsetV;
    }

    /**
     * 获取动画绑定对象
     */
    public get object(): TransformNode {
        return this._object;
    }

    /**
     * 获取位置进程值（0-1）
     */
    public get positionT(): number {
        return this._positionT;
    }

    /**
     * 设置位置进程值（0-1）
     */
    public set positionT(t: number) {
        this._positionT = Math.max(Math.min(1, t), 0);
    }

    /**
     * 获取上一个进程值（0-1）
     */
    public get previousT(): number {
        return this._preT;
    }

    /**
     * 获取运动速度值（0-1）
     */
    public get speedT(): number {
        return this._speedT;
    }

    /**
     * 设置运动速度值（0-1）
     */
    public set speedT(t: number) {
        this._speedT = Math.max(Math.min(1, t), -1);
    }

    /**
     * 获取路径u径向偏移值
     */
    public get offsetU(): number {
        return this._offsetU;
    }

    /**
     * 设置路径u径向偏移值
     */
    public set offsetU(u: number) {
        this._offsetU = u;
    }

    /**
     * 获取路径v径向偏移值
     */
    public get offsetV(): number {
        return this._offsetV;
    }

    /**
     * 设置路径v径向偏移值
     */
    public set offsetV(v: number) {
        this._offsetV = v;
    }

    /**
     * 设置步进回调函数
     */
    public set onStep(func: IStepCallback | null) {
        this._stepCallback = func;
    }

    /**
     * 设置同步回调函数
     */
    public set onSynchronize(func: ISynchronizeCallback | null) {
        this._syncCallback = func;
    }

    /**
     * 3d对象相等函数
     * @param target 3d物体对象
     * @returns 是否是同一对象
     */
    public equalObject(target: TransformNode): boolean {
        return this._object.id === target.id;
    }

    /**
     * 步进
     * @param t 步进值
     * @param reciprocal 倒放
     */
    public stepIn(t: number, reciprocal: boolean): void {
        if (t === 0) return;

        this._preT = this._positionT;
        this._positionT += this._speedT * t * this._speedDir;

        if (this._positionT > 1) {
            if (reciprocal) {
                this._speedDir *= -1;
                this._positionT = 1 - (this._positionT - 1);
            } else {
                this._positionT -= 1;
            }
        }
        if (this._positionT < 0) {
            if (reciprocal) {
                this._speedDir *= -1;
                this._positionT = -this._positionT;
            } else {
                this._positionT += 1;
            }
        }
        if (this._stepCallback) this._stepCallback(this._preT, this._positionT);
    }

    /**
     * 同步
     * @param points 路径点集
     * @param container 容器对象
     */
    public synchronize(points: Vector3[], container: TransformNode): void {

        const object = this._object;
        const ct = this._positionT;
        const pos = this._tV0;
        const tan = this._tV1;
        const ofs = this._tV2;
        const rot = this._rot;
        const tm0 = this._tM0;
        const tm1 = this._tM1;
        const tq1 = this._tQ1;
        const idx = this._idx;

        const cwm = this._tM2.copyFrom(container.getWorldMatrix());
        Calculation.ratioPointOfPointsByLength(ct, points, pos, idx);
        tan.copyFrom(points[idx[1]]).subtractInPlace(points[idx[0]]).normalize();
        Quaternion.FromUnitVectorsToRef(Vector3.RightReadOnly, tan, rot);

        ofs.set(0, this._offsetV, this._offsetU);
        ofs.applyRotationQuaternionInPlace(rot);
        pos.addInPlace(ofs);
        Vector3.TransformCoordinatesToRef(pos, cwm, pos);

        cwm.getRotationMatrixToRef(tm0);
        tq1.fromRotationMatrix(tm0);
        tq1.multiplyToRef(rot, rot);

        if (object.parent) {
            const inv = tm1.copyFrom(object.parent.getWorldMatrix()).invert();
            Vector3.TransformCoordinatesToRef(pos, inv, pos);
            inv.getRotationMatrixToRef(tm0);
            Quaternion.FromRotationMatrixToRef(tm0, tq1);
            tq1.multiplyToRef(rot, rot);
        }
        object.position = pos;
        object.rotationQuaternion = rot;

        if (this._syncCallback) this._syncCallback(this);
    }

}

/**
 * 路径运动动画参数对象
 * @param closed 是否闭合
 * @param reciprocal 是否倒放
 */
interface IMoveAlongCurvePathOptions {
    closed?: boolean,
    reciprocal?: boolean,
}

/**
 * 调试设置对象
 * @param color 颜色
 */
interface IDebugInfomation {
    color?: Color3
}

/**
 * 路径运动动画
 */
class MoveAlongCurvePath {

    private _points: Vector3[] = [];
    private _scene: Scene;
    private _container: TransformNode;
    private _closed: boolean;
    private _reciprocal: boolean;
    private _targets: AnimateTarget[] = [];
    private _debug: LinesMesh | null = null;
    private _tV0 = new Vector3();
    private _tV1 = new Vector3();
    private _tV2 = new Vector3();
    private _tQ0 = new Quaternion();
    private _tQ1 = new Quaternion();
    private _tM0 = new Matrix();
    private _tM1 = new Matrix();
    private _idx: [number, number] = [0, 0];

    constructor(scene: Scene, points?: Vector3[], options?: IMoveAlongCurvePathOptions) {
        this._scene = scene;
        this._container = new TransformNode('move-along-curve-path' + Utils.id, scene, true);
        if (points) this._points = [...points];
        const ops = options || {};
        this._closed = !!ops.closed;
        this._reciprocal = !!ops.reciprocal;
        this.updatePath();
    }

    /**
     * 设置调试参数（null|undefined|false 则禁用调试）
     */
    public set debug(info: IDebugInfomation | null | undefined | false) {
        if (info) {
            this.debug = false;
            this._debug = MeshBuilder.CreateLines('move-along-curve-path-debug', {
                points: this._points,
            }, this._scene);
            this._debug.color = info.color || new Color3(1, 0, 0);
            this._debug.parent = this._container;
        } else {
            if (!this._debug) return;
            this._debug.parent = null;
            this._debug.material!.dispose();
            this._debug.dispose();
            this._debug = null;
        }
    }

    /**
     * 获取容器对象
     */
    public get container(): TransformNode {
        return this._container;
    }

    /**
     * 获取路径点集
     */
    public get path(): Vector3[] {
        return this._points;
    }

    /**
     * 更新路径点集
     */
    private updatePath(): void {
        const sus = this._points.length > 1;
        const ps = sus ? this._points : [new Vector3(0, 0, 0), new Vector3(1, 0, 0)];
        if (this._closed) ps.push(ps[0]);
    }

    /**
     * 添加动画对象
     * @param object 目标对象
     * @param speedT 运动速度值（0-1）
     * @param uOffset 路径u径向偏移值
     * @param vOffset 路径v径向偏移值
     * @returns 动画对象
     */
    public addTarget(object: TransformNode, speedT: number, uOffset: number, vOffset: number): AnimateTarget | null {
        for (let i = 0; i < this._targets.length; i++) {
            if (this._targets[i].equalObject(object)) return null;
        }
        const ao = new AnimateTarget(object, speedT, uOffset, vOffset);
        this._targets.push(ao);
        return ao;
    }

    /**
     * 移除动画对象
     * @param object 目标对象
     */
    public removeTarget(object: TransformNode): void {
        let idx = -1;
        for (let i = 0; i < this._targets.length; i++) {
            if (this._targets[i].equalObject(object)) continue;
            idx = i;
            break;
        }
        if (idx < 0) return;
        this._targets.splice(idx, 1);
    }

    /**
     * 清除动画对象
     */
    public clearTargets(): void {
        this._targets.length = 0;
    }

    /**
     * 步进
     * @param time 步进值
     */
    public stepIn(time: number): void {
        for (let i = 0; i < this._targets.length; i++) {
            const ao = this._targets[i];
            ao.stepIn(time, this._reciprocal);
            ao.synchronize(this._points, this._container);
        }
    }

    /**
     * 根据参数提取动画对象的变换信息
     * @param posT 位置进程值
     * @param uOffset 路径u径向值
     * @param vOffset 路径v径向值
     * @param positionOut 位置输出
     * @param quaternionOut 旋转输出
     */
    public extractTransform(posT: number, uOffset: number, vOffset: number, positionOut: Vector3, quaternionOut: Quaternion): void {
        const ps = this._points;
        const pos = this._tV0;
        const tan = this._tV1;
        const ofs = this._tV2;
        const rot = this._tQ0;
        const tm0 = this._tM0;
        const tq1 = this._tQ1;
        const idx = this._idx;

        Calculation.ratioPointOfPointsByLength(posT, ps, pos, idx);
        tan.copyFrom(ps[idx[1]]).subtractInPlace(ps[idx[0]]).normalize();
        Quaternion.FromUnitVectorsToRef(Vector3.RightReadOnly, tan, rot);
        ofs.set(0, vOffset, uOffset);
        ofs.applyRotationQuaternionInPlace(rot);
        pos.addInPlace(ofs);

        const mwd = this._tM1.copyFrom(this._container.getWorldMatrix());
        Vector3.TransformCoordinatesToRef(pos, mwd, pos);
        mwd.getRotationMatrixToRef(tm0);
        Quaternion.FromRotationMatrixToRef(tm0, tq1);
        tq1.multiplyToRef(rot, rot);

        if (positionOut) positionOut.copyFrom(pos);
        if (quaternionOut) quaternionOut.copyFrom(rot);
    }
}


export { MoveAlongCurvePath };
export type { IStepCallback, ISynchronizeCallback, IMoveAlongCurvePathOptions };